"""Tests for the BSB-Lan water heater platform."""

from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock

from bsblan import BSBLANError
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.water_heater import (
    ATTR_OPERATION_MODE,
    DOMAIN as WATER_HEATER_DOMAIN,
    SERVICE_SET_OPERATION_MODE,
    SERVICE_SET_TEMPERATURE,
    STATE_ECO,
    STATE_OFF,
    STATE_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er

from . import setup_with_selected_platforms

from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform

ENTITY_ID = "water_heater.bsb_lan"


@pytest.fixture
def mock_dhw_config_none(mock_bsblan: AsyncMock) -> None:
    """Mock coordinator to return None for dhw_config."""
    mock_bsblan.hot_water_config.return_value = None


@pytest.fixture
def mock_dhw_config_missing_attributes(mock_bsblan: AsyncMock) -> None:
    """Mock config without the temperature limit attributes."""
    mock_config = MagicMock()
    mock_config.reduced_setpoint = None
    mock_config.nominal_setpoint_max = None
    mock_bsblan.hot_water_config.return_value = mock_config


@pytest.fixture
def mock_dhw_config_missing_value_attribute(mock_bsblan: AsyncMock) -> None:
    """Mock config with objects that don't have 'value' attribute."""
    mock_config = MagicMock()
    mock_reduced_setpoint = MagicMock(spec=[])  # Empty spec means no attributes
    mock_nominal_setpoint_max = MagicMock(spec=[])  # Empty spec means no attributes
    mock_config.reduced_setpoint = mock_reduced_setpoint
    mock_config.nominal_setpoint_max = mock_nominal_setpoint_max
    mock_bsblan.hot_water_config.return_value = mock_config


@pytest.mark.parametrize(
    ("dhw_file"),
    [
        ("dhw_state.json"),
    ],
)
async def test_water_heater_states(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
    dhw_file: str,
) -> None:
    """Test water heater states with different configurations."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )
    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)


async def test_water_heater_no_dhw_capability(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test that no water heater entity is created when DHW capability is missing."""
    # Mock DHW data to simulate no water heater capability
    mock_bsblan.hot_water_state.return_value.operating_mode = None
    mock_bsblan.hot_water_state.return_value.nominal_setpoint = None
    mock_bsblan.hot_water_state.return_value.dhw_actual_value_top_temperature = None

    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    # Verify no water heater entity was created
    entities = er.async_entries_for_config_entry(
        entity_registry, mock_config_entry.entry_id
    )
    water_heater_entities = [
        entity for entity in entities if entity.domain == Platform.WATER_HEATER
    ]

    assert len(water_heater_entities) == 0


async def test_water_heater_entity_properties(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test the water heater entity properties."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    state = hass.states.get(ENTITY_ID)
    assert state is not None

    # Test when nominal setpoint is "10"
    mock_setpoint = MagicMock()
    mock_setpoint.value = 10
    mock_bsblan.hot_water_state.return_value.nominal_setpoint = mock_setpoint

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state.attributes.get("temperature") == 10


@pytest.mark.parametrize(
    ("mode", "bsblan_mode"),
    [
        (STATE_ECO, "Eco"),
        (STATE_OFF, "Off"),
        (STATE_ON, "On"),
    ],
)
async def test_set_operation_mode(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    mode: str,
    bsblan_mode: str,
) -> None:
    """Test setting operation mode."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    await hass.services.async_call(
        domain=WATER_HEATER_DOMAIN,
        service=SERVICE_SET_OPERATION_MODE,
        service_data={
            ATTR_ENTITY_ID: ENTITY_ID,
            ATTR_OPERATION_MODE: mode,
        },
        blocking=True,
    )

    mock_bsblan.set_hot_water.assert_called_once_with(operating_mode=bsblan_mode)


async def test_set_invalid_operation_mode(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting invalid operation mode."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    with pytest.raises(
        HomeAssistantError,
        match=r"Operation mode invalid_mode is not valid for water_heater\.bsb_lan\. Valid operation modes are: eco, off, on",
    ):
        await hass.services.async_call(
            domain=WATER_HEATER_DOMAIN,
            service=SERVICE_SET_OPERATION_MODE,
            service_data={
                ATTR_ENTITY_ID: ENTITY_ID,
                ATTR_OPERATION_MODE: "invalid_mode",
            },
            blocking=True,
        )


async def test_set_temperature(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting temperature."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    await hass.services.async_call(
        domain=WATER_HEATER_DOMAIN,
        service=SERVICE_SET_TEMPERATURE,
        service_data={
            ATTR_ENTITY_ID: ENTITY_ID,
            ATTR_TEMPERATURE: 50,
        },
        blocking=True,
    )

    mock_bsblan.set_hot_water.assert_called_once_with(nominal_setpoint=50)


async def test_set_temperature_failure(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting temperature with API failure."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    mock_bsblan.set_hot_water.side_effect = BSBLANError("Test error")

    with pytest.raises(
        HomeAssistantError, match="An error occurred while setting the temperature"
    ):
        await hass.services.async_call(
            domain=WATER_HEATER_DOMAIN,
            service=SERVICE_SET_TEMPERATURE,
            service_data={
                ATTR_ENTITY_ID: ENTITY_ID,
                ATTR_TEMPERATURE: 50,
            },
            blocking=True,
        )


async def test_operation_mode_error(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test operation mode setting with API failure."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    mock_bsblan.set_hot_water.side_effect = BSBLANError("Test error")

    with pytest.raises(
        HomeAssistantError, match="An error occurred while setting the operation mode"
    ):
        await hass.services.async_call(
            domain=WATER_HEATER_DOMAIN,
            service=SERVICE_SET_OPERATION_MODE,
            service_data={
                ATTR_ENTITY_ID: ENTITY_ID,
                ATTR_OPERATION_MODE: STATE_ECO,
            },
            blocking=True,
        )


async def test_water_heater_no_sensors(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test water heater when sensors are not available."""
    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    # Set all sensors to None to simulate missing sensors
    mock_bsblan.hot_water_state.return_value.operating_mode = None
    mock_bsblan.hot_water_state.return_value.dhw_actual_value_top_temperature = None
    mock_bsblan.hot_water_state.return_value.nominal_setpoint = None

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    # Should not crash and properties should return None
    state = hass.states.get(ENTITY_ID)
    assert state is not None
    assert state.attributes.get("current_operation") is None
    assert state.attributes.get("current_temperature") is None
    assert state.attributes.get("temperature") is None


@pytest.mark.parametrize(
    ("fixture_name", "test_description"),
    [
        ("mock_dhw_config_none", "no DHW config"),
        (
            "mock_dhw_config_missing_attributes",
            "DHW config with missing temperature attributes",
        ),
        (
            "mock_dhw_config_missing_value_attribute",
            "DHW config with objects missing value attribute",
        ),
    ],
)
async def test_water_heater_default_temperature_limits(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    fixture_name: str,
    test_description: str,
    request: pytest.FixtureRequest,
) -> None:
    """Test water heater uses default temperature limits when config is unavailable."""
    # Apply the fixture dynamically
    request.getfixturevalue(fixture_name)

    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    state = hass.states.get(ENTITY_ID)
    assert state is not None

    # Should use default temperature limits when config is not available
    assert state.attributes.get("min_temp") == 10.0  # Default minimum
    assert state.attributes.get("max_temp") == 65.0  # Default maximum


async def test_water_heater_custom_temperature_limits_from_config(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test water heater uses custom temperature limits from DHW config."""
    # Set custom temperature limit values directly on the mock
    mock_bsblan.hot_water_config.return_value.reduced_setpoint.value = 15.0
    mock_bsblan.hot_water_config.return_value.nominal_setpoint_max.value = 75.0

    await setup_with_selected_platforms(
        hass, mock_config_entry, [Platform.WATER_HEATER]
    )

    state = hass.states.get(ENTITY_ID)
    assert state is not None

    # Should use custom temperature limits from config
    assert (
        state.attributes.get("min_temp") == 15.0
    )  # Custom minimum from reduced_setpoint
    assert (
        state.attributes.get("max_temp") == 75.0
    )  # Custom maximum from nominal_setpoint_max
